/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    PredictionNode.java
 *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.classifiers.trees.adtree;

import weka.classifiers.trees.ADTree;
import weka.core.FastVector;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

import java.io.Serializable;
import java.util.Enumeration;

/**
 * Class representing a prediction node in an alternating tree.
 * 
 * @author Richard Kirkby (rkirkby@cs.waikato.ac.nz)
 * @version $Revision: 1.7 $
 */
public final class PredictionNode implements Serializable, Cloneable,
		RevisionHandler {

	/** for serialization */
	private static final long serialVersionUID = 6018958856358698814L;

	/** The prediction value stored in this node */
	private double value;

	/** The children of this node - any number of splitter nodes */
	private FastVector children;

	/**
	 * Creates a new prediction node.
	 * 
	 * @param newValue
	 *            the value that the node should store
	 */
	public PredictionNode(double newValue) {

		value = newValue;
		children = new FastVector();
	}

	/**
	 * Sets the prediction value of the node.
	 * 
	 * @param newValue
	 *            the value that the node should store
	 */
	public final void setValue(double newValue) {

		value = newValue;
	}

	/**
	 * Gets the prediction value of the node.
	 * 
	 * @return the value stored in the node
	 */
	public final double getValue() {

		return value;
	}

	/**
	 * Gets the children of this node.
	 * 
	 * @return a FastVector containing child Splitter object references
	 */
	public final FastVector getChildren() {

		return children;
	}

	/**
	 * Enumerates the children of this node.
	 * 
	 * @return an enumeration of child Splitter object references
	 */
	public final Enumeration children() {

		return children.elements();
	}

	/**
	 * Adds a child to this node. If possible will merge, and will perform a
	 * deep copy of the child tree.
	 * 
	 * @param newChild
	 *            the new child to add (will be cloned)
	 * @param addingTo
	 *            the tree that this node belongs to
	 */
	public final void addChild(Splitter newChild, ADTree addingTo) {

		// search for an equivalent child
		Splitter oldEqual = null;
		for (Enumeration e = children(); e.hasMoreElements();) {
			Splitter split = (Splitter) e.nextElement();
			if (newChild.equalTo(split)) {
				oldEqual = split;
				break;
			}
		}
		if (oldEqual == null) { // didn't find one so just add
			Splitter addChild = (Splitter) newChild.clone();
			setOrderAddedSubtree(addChild, addingTo);
			children.addElement(addChild);
		} else { // found one, so do a merge
			for (int i = 0; i < newChild.getNumOfBranches(); i++) {
				PredictionNode oldPred = oldEqual.getChildForBranch(i);
				PredictionNode newPred = newChild.getChildForBranch(i);
				if (oldPred != null && newPred != null)
					oldPred.merge(newPred, addingTo);
			}
		}
	}

	/**
	 * Clones this node. Performs a deep copy, recursing through the tree.
	 * 
	 * @return a clone
	 */
	public final Object clone() {

		PredictionNode clone = new PredictionNode(value);
		for (Enumeration e = children.elements(); e.hasMoreElements();)
			clone.children.addElement((Splitter) ((Splitter) e.nextElement())
					.clone());
		return clone;
	}

	/**
	 * Merges this node with another.
	 * 
	 * @param merger
	 *            the node that is merging with this node - will not be
	 *            affected, will instead be cloned
	 * @param mergingTo
	 *            the tree that this node belongs to
	 */
	public final void merge(PredictionNode merger, ADTree mergingTo) {

		value += merger.value;
		for (Enumeration e = merger.children(); e.hasMoreElements();) {
			addChild((Splitter) e.nextElement(), mergingTo);
		}
	}

	/**
	 * Sets the order added values of the subtree rooted at this splitter node.
	 * 
	 * @param addChild
	 *            the root of the subtree
	 * @param addingTo
	 *            the tree that this node will belong to
	 */
	private final void setOrderAddedSubtree(Splitter addChild, ADTree addingTo) {

		addChild.orderAdded = addingTo.nextSplitAddedOrder();
		for (int i = 0; i < addChild.getNumOfBranches(); i++) {
			PredictionNode node = addChild.getChildForBranch(i);
			if (node != null)
				for (Enumeration e = node.children(); e.hasMoreElements();)
					setOrderAddedSubtree((Splitter) e.nextElement(), addingTo);
		}
	}

	/**
	 * Returns the revision string.
	 * 
	 * @return the revision
	 */
	public String getRevision() {
		return RevisionUtils.extract("$Revision: 1.7 $");
	}
}
